/**
 * Software License, Version 1.0
 * 
 * Copyright 2008 The Trustees of Indiana University.  All rights reserved.
 * 
 *
 *Redistribution and use in source and binary forms, with or without 
 *modification, are permitted provided that the following conditions are met:
 *
 *1) All redistributions of source code must retain the above copyright notice,
 * the list of authors in the original source code, this list of conditions and
 * the disclaimer listed in this license;
 *2) All redistributions in binary form must reproduce the above copyright 
 * notice, this list of conditions and the disclaimer listed in this license in
 * the documentation and/or other materials provided with the distribution;
 *3) Any documentation included with all redistributions must include the 
 * following acknowledgment:
 *
 *"This product includes software developed by the Community Grids Lab. For 
 * further information contact the Community Grids Lab at 
 * http://communitygrids.iu.edu/."
 *
 * Alternatively, this acknowledgment may appear in the software itself, and 
 * wherever such third-party acknowledgments normally appear.
 * 
 *4) The name Indiana University or Community Grids Lab or Granules, 
 * shall not be used to endorse or promote products derived from this software 
 * without prior written permission from Indiana University.  For written 
 * permission, please contact the Advanced Research and Technology Institute 
 * ("ARTI") at 351 West 10th Street, Indianapolis, Indiana 46202.
 *5) Products derived from this software may not be called Granules, 
 * nor may Indiana University or Community Grids Lab or Granules appear
 * in their name, without prior written permission of ARTI.
 * 
 *
 * Indiana University provides no reassurances that the source code provided 
 * does not infringe the patent or any other intellectual property rights of 
 * any other entity.  Indiana University disclaims any liability to any 
 * recipient for claims brought by any other entity based on infringement of 
 * intellectual property rights or otherwise.  
 *
 * LICENSEE UNDERSTANDS THAT SOFTWARE IS PROVIDED "AS IS" FOR WHICH NO 
 * WARRANTIES AS TO CAPABILITIES OR ACCURACY ARE MADE. INDIANA UNIVERSITY GIVES
 * NO WARRANTIES AND MAKES NO REPRESENTATION THAT SOFTWARE IS FREE OF 
 * INFRINGEMENT OF THIRD PARTY PATENT, COPYRIGHT, OR OTHER PROPRIETARY RIGHTS. 
 * INDIANA UNIVERSITY MAKES NO WARRANTIES THAT SOFTWARE IS FREE FROM "BUGS", 
 * "VIRUSES", "TROJAN HORSES", "TRAP DOORS", "WORMS", OR OTHER HARMFUL CODE.  
 * LICENSEE ASSUMES THE ENTIRE RISK AS TO THE PERFORMANCE OF SOFTWARE AND/OR 
 * ASSOCIATED MATERIALS, AND TO THE PERFORMANCE AND VALIDITY OF INFORMATION 
 * GENERATED USING SOFTWARE.
 */
package cgl.granules.exchange;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;

import cgl.granules.exception.MarshallingException;
import cgl.granules.util.MarshallingUtil;

/**
 * This is the base class for exchanges. All specific exchanges need to extend
 * this class and override the abstract methods. This class utilizes the
 * template pattern to marshall and reconstruct the various exchange types.
 * 
 * @author Shrideep Pallickara
 * 
 */

public abstract class Exchange {

	private final int REQUEST = 1;
	private final int RESPONSE = 2;

	private int requestResponseIndicator = 0;
	private int exchangeType = 0;
	private int exchangeSubType = 0;
	private int conditionFlag = 0;
	private long timestamp = 0;
	private String identifier = "";
	private String description = "";
	private String respondTo;

	/**
	 * Retrieve the byte[] representation of the exchange.
	 * 
	 * @return The byte[] representation of the exchange.
	 * @throws IOException
	 *             If there are IO problems while performing this operation.
	 * @throws MarshallingException
	 *             If there are Marshalling problems while performing this
	 *             operation.
	 */
	public final byte[] getBytes() throws IOException, MarshallingException {
		byte[] marshalledBytes = null;
		ByteArrayOutputStream baOutputStream = new ByteArrayOutputStream();
		DataOutputStream dout = new DataOutputStream(new BufferedOutputStream(
				baOutputStream));

		byte[] baseClassBytes = marshallBaseClass();
		dout.writeInt(baseClassBytes.length);
		dout.write(baseClassBytes);

		byte[] derivedClassBytes = marshallExchangeSpecificElements();
		if (derivedClassBytes == null) {
			dout.writeInt(0);
		} else {
			dout.writeInt(derivedClassBytes.length);
			dout.write(derivedClassBytes);
		}

		dout.flush();
		marshalledBytes = baOutputStream.toByteArray();
		return marshalledBytes;
	}

	/**
	 * Reconstruct the exchange from the byte[] representation.
	 * 
	 * @param marshalledBytes
	 *            The byte[] representation of the exchange.
	 * @throws IOException
	 *             If there are IO problems while performing this operation.
	 * @throws MarshallingException
	 *             If there are Marshalling problems while performing this
	 *             operation.
	 */
	public final void reconstruct(byte[] marshalledBytes) throws IOException,
			MarshallingException {
		ByteArrayInputStream baInputStream = new ByteArrayInputStream(
				marshalledBytes);
		DataInputStream din = new DataInputStream(new BufferedInputStream(
				baInputStream));

		byte[] baseClassBytes = new byte[din.readInt()];
		din.readFully(baseClassBytes);
		reconstructBaseClass(baseClassBytes);

		int derivedClassLength = din.readInt();
		if (derivedClassLength != 0) {
			byte[] derivedClassBytes = new byte[derivedClassLength];
			din.readFully(derivedClassBytes);
			unmarshallExchangeSpecificElements(derivedClassBytes);
		}

	}

	private void reconstructBaseClass(byte[] marshalledBytes)
			throws IOException, MarshallingException {
		ByteArrayInputStream baInputStream = new ByteArrayInputStream(
				marshalledBytes);
		DataInputStream din = new DataInputStream(new BufferedInputStream(
				baInputStream));
		MarshallingUtil marshaller = MarshallingUtil.getInstance();

		exchangeType = din.readInt();
		exchangeSubType = din.readInt();
		requestResponseIndicator = din.readInt();
		conditionFlag = din.readInt();
		timestamp = din.readLong();

		boolean hasIdentifier = din.readBoolean();
		if (hasIdentifier) {
			identifier = marshaller.readString(din);
		}

		boolean hasDescription = din.readBoolean();
		if (hasDescription) {
			description = marshaller.readString(din);
		}

		boolean hasRespondTo = din.readBoolean();
		if (hasRespondTo) {
			respondTo = marshaller.readString(din);
		}
	}

	private byte[] marshallBaseClass() throws IOException, MarshallingException {
		MarshallingUtil marshaller = MarshallingUtil.getInstance();
		byte[] marshalledBytes = null;
		ByteArrayOutputStream baOutputStream = new ByteArrayOutputStream();
		DataOutputStream dout = new DataOutputStream(new BufferedOutputStream(
				baOutputStream));

		dout.writeInt(exchangeType);
		dout.writeInt(exchangeSubType);
		dout.writeInt(requestResponseIndicator);
		dout.writeInt(conditionFlag);
		dout.writeLong(timestamp);

		/* Process the identifier element */
		boolean hasIdentifier = false;
		if (identifier != null) {
			hasIdentifier = true;
		}
		dout.writeBoolean(hasIdentifier);
		if (hasIdentifier) {
			marshaller.writeString(dout, identifier);
		}

		/* Process the description element */
		boolean hasDescription = false;
		if (description != null) {
			hasDescription = true;
		}
		dout.writeBoolean(hasDescription);
		if (hasDescription) {
			marshaller.writeString(dout, description);
		}

		/* Process the respondTo element */
		boolean hasRespondTo = false;
		if (respondTo != null) {
			hasRespondTo = true;
		}
		dout.writeBoolean(hasRespondTo);
		if (hasRespondTo) {
			marshaller.writeString(dout, respondTo);
		}

		dout.flush();
		marshalledBytes = baOutputStream.toByteArray();
		return marshalledBytes;

	}

	/**
	 * Checks to see if the exchange sub-type in the reconstructed exchange is a
	 * valid one.
	 * 
	 * @return <code>true</code> If the exchange subType is a valid one, or
	 *         <code>false</code> otherwise.
	 */
	public abstract boolean isValidExchangeSubType();

	/**
	 * This method is utilized by the derived classes to marshall (or serialize)
	 * elements that are specific to the derived class. All the base class
	 * elements (which are identified by the accessor methods) are handled by
	 * the base class even though it is an abstract class.
	 * 
	 * @return The byte[] representation of the derived classes.
	 * @throws IOException
	 *             If there are IO problems while performing this operation.
	 * @throws MarshallingException
	 *             If there are Marshalling problems while performing this
	 *             operation.
	 */
	public abstract byte[] marshallExchangeSpecificElements()
			throws IOException, MarshallingException;

	/**
	 * This method is utilized by the derived classes to reconstruct elements
	 * that are specific to the derived class. The unmarshall method will
	 * reconstruct the object state based on what was marshalled using the
	 * marshall method. All the base class elements (which are identified by the
	 * accessor methods) are handled by the base class even though it is an
	 * abstract class.
	 * 
	 * @param exchangeSpecificBytes
	 *            The byte[] representation of the derived class.
	 * @throws IOException
	 *             If there are IO problems while performing this operation.
	 * @throws MarshallingException
	 *             If there are Marshalling problems while performing this
	 *             operation.
	 */
	public abstract void unmarshallExchangeSpecificElements(
			byte[] exchangeSpecificBytes) throws IOException,
			MarshallingException;

	/**
	 * @param exchangeType
	 *            the exchangeType to set
	 */
	public void setExchangeType(int exchangeType) {
		this.exchangeType = exchangeType;
	}

	/**
	 * @return the exchangeType
	 */
	public int getExchangeType() {
		return exchangeType;
	}

	/**
	 * Indicates whether this exchange is a Request
	 * 
	 * @return <code>true</code> if this exchange is a request;
	 *         <code>false</code> otherwise.
	 */
	public boolean isRequest() {
		if (requestResponseIndicator == REQUEST) {
			return true;
		}
		return false;
	}

	/**
	 * Indicates whether this exchange is a Response to a previously issued
	 * request.
	 * 
	 * @return <code>true</code> if the exchange is a reponse;<code>false</code>
	 *         otherwise.
	 */
	public boolean isResponse() {
		if (requestResponseIndicator == RESPONSE) {
			return true;
		}
		return false;
	}

	/**
	 * Sets this exchange as a request.
	 */
	protected void setRequest() {
		requestResponseIndicator = REQUEST;
	}

	/**
	 * Sets this exchange as a reponse.
	 */
	protected void setResponse() {
		requestResponseIndicator = RESPONSE;
	}

	/**
	 * @param exchangeSubType
	 *            the exchangeSubType to set
	 */
	protected void setExchangeSubType(int exchangeSubType) {
		this.exchangeSubType = exchangeSubType;
	}

	/**
	 * @return the exchangeSubType
	 */
	public int getExchangeSubType() {
		return exchangeSubType;
	}

	/**
	 * @param conditionFlag
	 *            the conditionFlag to set
	 */
	protected void setConditionFlag(int conditionFlag) {
		this.conditionFlag = conditionFlag;
	}

	/**
	 * @return the conditionFlag
	 */
	public int getConditionFlag() {
		return conditionFlag;
	}

	/**
	 * @param identifier
	 *            the identifier to set
	 */
	protected void setIdentifier(String identifier) {
		this.identifier = identifier;
	}

	/**
	 * @return the identifier
	 */
	public String getIdentifier() {
		return identifier;
	}

	/**
	 * @param description
	 *            the description to set
	 */
	protected void setDescription(String description) {
		this.description = description;
	}

	/**
	 * Retrieve the description associated with this exchange. Typically, this
	 * field is used to summarize the exchange in plain English.
	 * 
	 * @return the description
	 */
	public String getDescription() {
		return description;
	}

	/**
	 * Set the repondTo field for this exchange.
	 * 
	 * @param respondTo
	 *            the respondTo to set
	 */
	public void setRespondTo(String respondTo) {
		this.respondTo = respondTo;
	}

	/**
	 * Retrieve the respondTo field for this exchange.
	 * 
	 * @return the respondTo
	 */
	public String getRespondTo() {
		return respondTo;
	}

	/**
	 * Set the timestamp for this exchange.
	 * 
	 * @param timestamp
	 *            the timestamp to set
	 */
	public void setTimestamp(long timestamp) {
		this.timestamp = timestamp;
	}

	/**
	 * Retrieve the timestamp for this exchange.
	 * 
	 * @return the timestamp
	 */
	public long getTimestamp() {
		return timestamp;
	};

}
